/**
  ******************************************************************************
  * @file    IIC_Simulate_Config.c
  * @brief   I2C总线模拟驱动（软件实现）
  * @author  18408107475@163.com
  * @version V1.0
  * @date    2025-6-6
  * @note    适用于STM32F1系列，通过GPIO模拟I2C时序
  *          移植说明：
  *          1. 修改IIC_SCL_PORT/PIN和IIC_SDA_PORT/PIN为实际使用的引脚
  *          2. 调整Delay_us()函数为实际的微秒延时函数
  ******************************************************************************
  */

#include "IIC_Simulate_Config.h"

/**
  * @brief  SCL引脚配置
  * @param  无
  * @retval 无
  * @note   配置SCL引脚为开漏输出模式，需外部上拉电阻
  */
static void IIC_SCL_Config(void)
{
	/* 使能GPIO时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    /* 配置SCL引脚为开漏输出 */
    GPIO_InitTypeDef GPIO_InitStructure = {
		.GPIO_Mode  = GPIO_Mode_Out_OD,
		.GPIO_Pin   = IIC_SCL_PIN,
		.GPIO_Speed = GPIO_Speed_50MHz
	};

    GPIO_Init(IIC_SCL_PORT, &GPIO_InitStructure);
}

/**
  * @brief  SDA引脚配置
  * @param  mode: 引脚模式(GPIO_Mode_Out_OD或GPIO_Mode_IN_FLOATING)
  * @retval 无
  * @note   发送数据时配置为输出，接收数据时配置为浮空输入
  */
static void IIC_SDA_Config(GPIOMode_TypeDef mode)
{
    /* 使能GPIO时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    /* 配置SDA引脚模式 */
    GPIO_InitTypeDef GPIO_InitStructure = {
		.GPIO_Mode  = mode,
		.GPIO_Pin   = IIC_SDA_PIN,
		.GPIO_Speed = GPIO_Speed_50MHz
	};

	GPIO_Init(IIC_SDA_PORT, &GPIO_InitStructure);
}

/**
  * @brief  I2C总线初始化
  * @param  无
  * @retval 无
  * @note   初始化后总线处于空闲状态(SCL和SDA都为高电平)
  */
void IIC_Init(void)
{
    IIC_SCL_Config();
    IIC_SDA_Config(GPIO_Mode_Out_OD);
    
    /* 总线空闲状态 */
    SDA_SET(1);
    SCL_SET(1);
    IIC_Delay();
}

/**
  * @brief  产生I2C起始信号
  * @param  无
  * @retval 无
  * @note   起始条件：SCL高电平时，SDA从高变低
  */
void IIC_Start(void)
{
    IIC_SDA_Config(GPIO_Mode_Out_OD);  // 确保SDA为输出模式
    
    SDA_SET(1);
    SCL_SET(1);
    IIC_Delay();
    
    SDA_SET(0);  // START条件: SDA在SCL高电平时从高变低
    IIC_Delay();
    
    SCL_SET(0);  // 准备发送数据
    IIC_Delay();
}

/**
  * @brief  产生I2C停止信号
  * @param  无
  * @retval 无
  * @note   停止条件：SCL高电平时，SDA从低变高
  */
void IIC_Stop(void)
{
    IIC_SDA_Config(GPIO_Mode_Out_OD);  // 确保SDA为输出模式
    
    SDA_SET(0);
    SCL_SET(0);
    IIC_Delay();
    
    SCL_SET(1);
    IIC_Delay();
    
    SDA_SET(1);  // STOP条件: SDA在SCL高电平时从低变高
    IIC_Delay();
}

/**
  * @brief  发送一个字节
  * @param  byte: 要发送的数据
  * @retval 无
  * @note   数据在SCL低电平时变化，高电平时稳定
  */
void IIC_Send_Byte(u8 byte)
{
    IIC_SDA_Config(GPIO_Mode_Out_OD);
    
    SCL_SET(0);
    IIC_Delay();
    
    /* 发送8位数据，MSB先行 */
    for(u8 i = 0; i < 8; i++)
    {
        (byte & 0x80) ? SDA_SET(1) : SDA_SET(0);  // 发送最高位
        byte <<= 1;
        IIC_Delay();  // 数据建立时间
        
        SCL_SET(1);
        IIC_Delay();  // SCL高电平保持时间
        
        SCL_SET(0);
        IIC_Delay();  // SCL低电平保持时间
    }
}

/**
  * @brief  等待从机应答
  * @param  无
  * @retval bool: true-收到应答, false-未收到应答
  * @note   在第9个时钟周期检测SDA是否为低电平
  */
bool IIC_Wait_Ack(void)
{
    u8 timeout = 0;
    
    IIC_SDA_Config(GPIO_Mode_IN_FLOATING);  // 切换SDA为输入模式
    
    SCL_SET(0);
    IIC_Delay();
    
    SCL_SET(1);
    IIC_Delay();  // 从机应在此时拉低SDA
    
    /* 检测SDA电平 */
    while(SDA_READ)
    {
        if(timeout++ > 250)
        {
            IIC_Stop();  // 超时则停止传输
            return false;
        }
        IIC_Delay();
    }
    
    SCL_SET(0);
    IIC_Delay();
    
    IIC_SDA_Config(GPIO_Mode_Out_OD);  // 恢复SDA为输出模式
    return true;
}

/**
  * @brief  主机产生应答信号
  * @param  无
  * @retval 无
  * @note   在第9个时钟周期拉低SDA
  */
void IIC_Ack(void)
{
    IIC_SDA_Config(GPIO_Mode_Out_OD);
    
    SCL_SET(0);
    IIC_Delay();
    
    SDA_SET(0);  // 应答信号
    IIC_Delay();
    
    SCL_SET(1);
    IIC_Delay();
    
    SCL_SET(0);
    IIC_Delay();
}

/**
  * @brief  主机产生非应答信号
  * @param  无
  * @retval 无
  * @note   在第9个时钟周期保持SDA为高
  */
void IIC_NAck(void)
{
    IIC_SDA_Config(GPIO_Mode_Out_OD);
    
    SCL_SET(0);
    IIC_Delay();
    
    SDA_SET(1);  // 非应答信号
    IIC_Delay();
    
    SCL_SET(1);
    IIC_Delay();
    
    SCL_SET(0);
    IIC_Delay();
}

/**
  * @brief  读取一个字节
  * @param  ack: 是否发送应答
  * @retval u8: 读取到的数据
  * @note   数据在SCL高电平时稳定，低电平时变化
  */
u8 IIC_Read_Byte(u8 ack)
{
    u8 byte = 0;
    
    IIC_SDA_Config(GPIO_Mode_IN_FLOATING);  // SDA设为输入模式
    
    SCL_SET(0);
    IIC_Delay();

    /* 读取8位数据，MSB先行 */
    for(u8 i = 0; i < 8; i++)
    {
        SCL_SET(1);
        IIC_Delay();
        
        byte <<= 1;
        if(SDA_READ) byte |= 0x01;
        
        SCL_SET(0);
        IIC_Delay();
    }
    
    /* 发送应答/非应答 */
    if(ack)	IIC_Ack();
    else	IIC_NAck();
    
    return byte;
}

/**
  * @brief  向设备写数据
  * @param  dev_addr: 设备地址(7位)
  * @param  reg_addr: 寄存器地址
  * @param  data: 要写入的数据
  * @retval bool: true-写入成功, false-写入失败
  * @note   完整写入时序：开始->发送设备地址(写)->发送寄存器地址->发送数据->停止
  */
bool IIC_Write_Data(u8 dev_addr, u8 reg_addr, u8 data)
{
    IIC_Start();
    IIC_Send_Byte(dev_addr << 1);  // 设备地址 + 写标志(0)
    if(!IIC_Wait_Ack()) return false;
    
    IIC_Send_Byte(reg_addr);
    if(!IIC_Wait_Ack()) return false;
    
    IIC_Send_Byte(data);
    if(!IIC_Wait_Ack()) return false;
    
    IIC_Stop();
    return true;
}

/**
  * @brief  从设备读数据
  * @param  dev_addr: 设备地址(7位)
  * @param  reg_addr: 寄存器地址
  * @retval u8: 读取到的数据
  * @note   完整读取时序：开始->发送设备地址(写)->发送寄存器地址->重复开始->发送设备地址(读)->读取数据->停止
  */
u8 IIC_Read_Data(u8 dev_addr, u8 reg_addr)
{
    u8 data;
    
    /* 先写入寄存器地址 */
    IIC_Start();
    IIC_Send_Byte(dev_addr << 1);  // 设备地址 + 写标志(0)
    IIC_Wait_Ack();
    IIC_Send_Byte(reg_addr);
    IIC_Wait_Ack();
    
    /* 然后读取数据 */
    IIC_Start();
    IIC_Send_Byte((dev_addr << 1) | 0x01);  // 设备地址 + 读标志(1)
    IIC_Wait_Ack();
    data = IIC_Read_Byte(0);  // 最后不发送ACK
    IIC_Stop();
    
    return data;
}

/********************************* 使用示例 ************************************
// 需要用户实现的微秒延时函数
void Delay_us(uint32_t us)
{
    // 实际实现取决于使用的定时器或系统时钟
}

int main(void)
{
    // 初始化I2C
    IIC_Init();
    
    // 示例：向地址0x50的设备(如EEPROM)的0x00地址写入数据0x55
    IIC_Write_Data(0x50, 0x00, 0x55);
    
    // 示例：从地址0x50的设备(如EEPROM)的0x00地址读取数据
    u8 value = IIC_Read_Data(0x50, 0x00);
    
    while(1)
    {
        // 主循环
    }
}
******************************************************************************/
